;; SPDX-FileCopyrightText: 2021 pukkamustard <pukkamustard@posteo.net>
;; SPDX-FileCopyrightText: 2022 Maxime Devos <maximedevos@telenet.be>
;;
;; SPDX-License-Identifier: AGPL-3.0-or-later

;; This module provides an interface to the GNUnet DHT service for storing and
;; retrieving blocks. This can be used to store blocks of ERIS encoded content.
;; TODO: the FS service has some fanciness w.r.t. reputation and not storing
;; everything on DHT directly, instead contacting relevant peers with CADET.
;;
;; Scheme-GNUnet is AGPL, so this module is AGPL (and not GPL) too.

(define-module (eris blocks gnunet-dht)
  #:use-module (gnu gnunet config fs)
  #:use-module (gnu gnunet dht client)
  #:use-module (gnu gnunet utils bv-slice)
  #:use-module (gnu gnunet netstruct syntactic)
  #:use-module (gnu gnunet hashcode struct)
  #:use-module (fibers conditions)
  #:use-module (srfi srfi-71)
  #:use-module (rnrs bytevectors)

  #:export (eris-gnunet-dht-server
	    eris-blocks-gnunet-dht-reducer
            eris-blocks-gnunet-dht-ref))

;; Connection to the DHT service, made with 'connect'
;; from (gnu gnunet dht client)
(define eris-gnunet-dht-server (make-parameter #f))

(define (current-dht-server)
  (or (eris-gnunet-dht-server)
      (error "not connected to the DHT service, set 'eris-gnunet-dht-server'!'")))

;; TODO: register a dedicated block type instead of using
;; block:test
(define eris-block-type 8)

(define (reference->dht-key ref)
  (define key/extended (make-slice/read-write (sizeof /hashcode:512 '())))
  (slice-copy! (bv-slice/read-write ref)
	       (slice-slice key/extended 0 (bytevector-length ref)))
  (slice/read-only key/extended))

(define (gnunet-block-put key value)
  "Store a block on GNUnet's DHT."
  ;; TODO: set the expiration time and replication level appropriately.
  ;; TODO: make-datum and put! assume that the bytevector slice isn't modified
  ;; until it is sent; verify that 'value' aren't modified or make a copy.
  (put! (current-dht-server)
	(datum->insertion
	 (make-datum eris-block-type
		     (reference->dht-key key)
		     (bv-slice/read-write value)))))

;; GNUnet block reducer

(define eris-blocks-gnunet-dht-reducer
  (case-lambda
    ;; Initialization. Nothing to do here.  In an improved implementation,
    ;; we might want to limit the number of blocks that are currently
    ;; being sent to the service, to keep memory usage from growing
    ;; indefinitely when the service is slow.
    (() '())

    ;; Completion. Again, nothing to do.
    ((_) 'done)

    ;; store a block
    ((_ ref-block)
     (gnunet-block-put (car ref-block) (cdr ref-block)))))

(define (eris-blocks-gnunet-dht-ref ref)
  "Dereference a block from GNUnet's DHT"
  ;; TODO: implement timeouts!
  ;; TODO: it would be nice if guile-eris could fetch multiple blocks
  ;; in parallel.
  (define query
    (make-query eris-block-type (reference->dht-key ref)))
  (define response)
  (define received (make-condition))
  (define (found search-result)
    ;; TODO: verify the value matches the key.
    ;; A malicious peer or a peer with broken memory
    ;; could put invalid key->value mappings into the DHT,
    ;; which should ideally be ignored in favour of correct
    ;; mappings ...
    (define value (datum-value (search-result->datum search-result)))
    (define value/bv (make-bytevector (slice-length value)))
    (slice-copy! value (bv-slice/read-write value/bv))
    (set! response value/bv)
    (signal-condition! received))
  (define get-handle (start-get! (current-dht-server) query found))
  (wait received)
  ;; TODO: cancel the get-handle to avoid leaking memory
  ;; (not yet implemented in gnunet-scheme ...)
  response)
